/**
 * Copyright 2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.fos.disk;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.jianggujin.fos.JBucket;
import com.jianggujin.fos.JClient;
import com.jianggujin.fos.JFOSException;
import com.jianggujin.fos.JListObjectsRequest;
import com.jianggujin.fos.JObject;
import com.jianggujin.fos.JObjectListing;
import com.jianggujin.fos.JObjectMetadata;
import com.jianggujin.fos.util.JObjectUtils;
import com.qiniu.common.QiniuException;

import lombok.Getter;
import lombok.NonNull;

/**
 * 本地硬盘客户端
 * 
 * @author jianggujin
 *
 */
@Getter
public class JDiskClient implements JClient {
    private JDiskConfiguration configuration;

    public JDiskClient(@NonNull JDiskConfiguration configuration) throws JFOSException {
        this.configuration = configuration;
        if (!configuration.getBucketRoot().exists()) {
            if (!configuration.getBucketRoot().mkdirs()) {
                throw new JFOSException("create buckets root fail");
            }
        }
    }

    @Override
    public List<JBucket> listBuckets() throws JFOSException {
        File[] buckets = configuration.getBucketRoot().listFiles(item -> item.isDirectory());
        if (buckets == null || buckets.length == 0) {
            return Collections.emptyList();
        }
        List<JBucket> list = new ArrayList<JBucket>(buckets.length);
        for (File bucket : buckets) {
            list.add(new JDiskBucket(bucket));
        }
        return list;
    }

    @Override
    public JBucket createBucket(String bucketName) throws JFOSException {
        File bucket = new File(configuration.getBucketRoot(), bucketName);
        if (bucket.exists()) {
            if (bucket.isFile()) {
                throw new JFOSException("a file with the same name already exists[" + bucketName + "]");
            }
            throw new JFOSException("bucket exists[" + bucketName + "]");
        }
        if (!bucket.mkdirs()) {
            throw new JFOSException("create bucket fail[" + bucketName + "]");
        }
        return new JDiskBucket(bucket);
    }

    @Override
    public void deleteBucket(String bucketName) throws JFOSException {
        File bucket = new File(configuration.getBucketRoot(), bucketName);
        if (!bucket.exists() || bucket.isFile()) {
            throw new JFOSException("bucket not exists[" + bucketName + "]");
        }
        if (!deleteFile(bucket)) {
            throw new JFOSException("delete bucket fail[" + bucketName + "]");
        }
    }

    private static boolean deleteFile(File file) {
        if (file.isFile()) {
            return file.delete();
        } else {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (!deleteFile(f)) {
                        return false;
                    }
                }
            }
            return file.delete();
        }
    }

    @Override
    public boolean doesBucketExist(String bucketName) throws JFOSException {
        File bucket = new File(configuration.getBucketRoot(), bucketName);
        return bucket.exists() && bucket.isDirectory();
    }

    @Override
    public JObjectListing listObjects(String bucketName) throws JFOSException {
        return listObjects(bucketName, null, null, null, null);
    }

    @Override
    public JObjectListing listObjects(String bucketName, String prefix) throws JFOSException {
        return listObjects(bucketName, prefix, null, null, null);
    }

    @Override
    public JObjectListing listObjects(JListObjectsRequest request) throws JFOSException {
        return listObjects(request.getBucketName(), request.getPrefix(), request.getMarker(), request.getMaxKeys(),
                request.getDelimiter());
    }

    public JObjectListing listObjects(String bucketName, String prefix, String marker, Integer maxKeys,
            String delimiter) throws JFOSException {
        throw new JFOSException("unsupport listObjects");
    }

    @Override
    public JObjectMetadata getObjectMetadata(String bucketName, String key) throws JFOSException {
        File object = getObjectFile(bucketName, key);
        if (!object.exists() || object.isDirectory()) {
            throw new JFOSException("object not exists[" + key + "]");
        }
        return new JDiskObjectMetadata(object);
    }

    @Override
    public void deleteObject(String bucketName, String key) throws JFOSException {
        File object = getObjectFile(bucketName, key);
        if (!object.exists() || object.isDirectory()) {
            throw new JFOSException("object not exists[" + key + "]");
        }
        if (!object.delete()) {
            throw new JFOSException("delete object fail[" + key + "]");
        }
    }

    @Override
    public List<String> deleteObjects(String bucketName, List<String> keys) throws JFOSException {
        if (keys == null || keys.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> list = new ArrayList<String>();
        for (String key : keys) {
            File object = getObjectFile(bucketName, key);
            if (!object.exists() || object.isDirectory()) {
                list.add(key);
                continue;
            }
            if (!object.delete()) {
                list.add(key);
                continue;
            }
        }
        return list;
    }

    @Override
    public void copyObject(String sourceBucketName, String sourceKey, String destinationBucketName,
            String destinationKey) throws JFOSException {
        File source = getObjectFile(sourceBucketName, sourceKey);
        if (!source.exists() || source.isDirectory()) {
            throw new JFOSException("object not exists[" + sourceKey + "]");
        }
        File destination = getObjectFile(destinationBucketName, destinationKey);
        try (FileOutputStream out = new FileOutputStream(destination); InputStream in = new FileInputStream(source)) {
            JObjectUtils.crossStream(in, out);
        } catch (IOException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JObject getObject(String bucketName, String key) throws JFOSException {
        File object = getObjectFile(bucketName, key);
        if (!object.exists() || object.isDirectory()) {
            throw new JFOSException("object not exists[" + key + "]");
        }
        return new JDiskObject(bucketName, key, object);
    }

    @Override
    public JObjectMetadata getObject(String bucketName, String key, File file) throws JFOSException {
        File object = getObjectFile(bucketName, key);
        if (!object.exists() || object.isDirectory()) {
            throw new JFOSException("object not exists[" + key + "]");
        }

        InputStream in = null;
        OutputStream outputStream = null;
        try {
            in = new FileInputStream(object);
            outputStream = new FileOutputStream(file);
            JObjectUtils.crossStream(in, outputStream);
            return new JDiskObjectMetadata(object);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        } catch (IOException e) {
            throw new JFOSException(e.getMessage(), e);
        } finally {
            JObjectUtils.close(outputStream);
            JObjectUtils.close(in);
        }
    }

    @Override
    public void putObject(String bucketName, String key, InputStream input) throws JFOSException {
        try (FileOutputStream out = new FileOutputStream(getObjectFile(bucketName, key)); InputStream in = input) {
            JObjectUtils.crossStream(in, out);
        } catch (IOException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void putObject(String bucketName, String key, InputStream input, String contentType) throws JFOSException {
        this.putObject(bucketName, key, input);
    }

    @Override
    public void putObject(String bucketName, String key, File file) throws JFOSException {
        try {
            this.putObject(bucketName, key, new FileInputStream(file));
        } catch (FileNotFoundException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void putObject(String bucketName, String key, File file, String contentType) throws JFOSException {
        try {
            this.putObject(bucketName, key, new FileInputStream(file));
        } catch (FileNotFoundException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public String generateUrl(String bucketName, String key, int expires) throws JFOSException {
        String encodedFileName = null;
        try {
            encodedFileName = URLEncoder.encode(key, "utf-8").replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
        }
        String domain = this.configuration.getDomainOfBucket().get(bucketName);
        if (domain == null) {
            throw new JFOSException("no such bucket domain");
        }
        return String.format("%s/%s", domain, encodedFileName);
    }

    @Override
    public void destory() {
    }

    private File getObjectFile(String bucketName, String key) {
        File bucket = new File(configuration.getBucketRoot(), bucketName);
        return new File(bucket, key);
    }
}
